3. Property 읽기, 쓰기 자동화(또는 단순화)

enum class EPropertyType : int
	{
		EPT_Float = 0,
		EPT_Boolean,
		EPT_String,
		EPT_Float3,
		EPT_StringArray,
		EPT_TypeNum
	};

	struct FProperty
	{
		using FPropertyValue = std::variant<
			float,
			XMFLOAT3,
			bool,
			std::string,
			std::vector<std::string>
		>;

		std::string Name;
		EPropertyType Type;
		FPropertyValue Value;
	};

위와 같이 Enum과 구조체, std::variant를 활용해서 프로퍼티를 읽고, 저장하고 있다. 문제는, 이것을 처리하는 것이아래와 같이 if, else로 이루어진 지옥문 코딩이라는 점이다.

void FBlueprintAsset::SerializeProperty(FXMLElement* PropertyElement, FBinaryWriter& Writer)
{
    const std::string PropertyType = PropertyElement->Name();

    Writer << PropertyElement->Attribute("Name");
    
    if (PropertyType == "float")
    {
        Writer << (int)EPropertyType::EPT_Float;
        const float Value = PropertyElement->FloatAttribute("Value");
        Writer << Value;
    }
    else if (PropertyType == "float3")
    {
        Writer << (int)EPropertyType::EPT_Float3;
        std::string Float3 = PropertyElement->Attribute("Value");
        XMFLOAT3 Value;
        sscanf_s(Float3.c_str(), "(%f, %f, %f)", &Value.x, &Value.y, &Value.z);
        Writer << Value;
    }
    else if (PropertyType == "bool")
    {
    
    ...
    
}

이런식으로 처리되고 있는 함수가 총 6개로, 새로운 타입을 추가할때마다 매번 처리해주어야 하는 비효율의 끝을 보여주고 있다.

그래서 C++의 Template 특수화, 폴드 표현식, 그리고 std::variant와 std::visit을 최대한 활용해서 이를 자동화 해보자고 한다.

아래와 같이 템플릿 특수화로 구조체를 만들어 주고,

template<typename T> struct PropertyTraits;

template<> struct PropertyTraits<bool>
{
	static constexpr const char* Tag = "bool";
	static constexpr EPropertyType Type = EPropertyType::EPT_Boolean;

	static bool Parse(FXMLElement* Element) { return Element->BoolAttribute("Value"); }
	};

템플릿과 폴드 표현식으로 아래와 같이 함수를 if, else를 제거하고 깔끔하게 만들 수 있다.

template<typename... Args>
void SerializePropertyHelper(FXMLElement* PropertyElement, FBinaryWriter& Writer, std::variant<Args...>)
{
    std::string Name = PropertyElement->Attribute("Name");
    Writer << Name;

    const std::string Tag = PropertyElement->Name();

    // 폴드 표현식
    bool bSuccess = ((Tag == PropertyTraits<Args>::Tag ? 
        (Writer << Type, Writer << PropertyTraits<Args>::Parse(PropertyElement), true) : false ||
    ...);


    assert(bSuccess && "Invalid property type");
}

void FBlueprintAsset::SerializeProperty(FXMLElement* PropertyElement, FBinaryWriter& Writer)
{
    SerializePropertyHelper(PropertyElement, Writer, FProperty::FPropertyValue{});
}

3.3. variant와 visit를 참고해서 visit을 활용해서 if, else를 줄일 수 있다.